package com.googlecode.jch.plugin.runconfiguration;

import com.googlecode.jch.execution.SolutionRunner;
import com.googlecode.jch.plugin.JCHUtil;
import com.intellij.execution.*;
import com.intellij.execution.configuration.EnvironmentVariablesComponent;
import com.intellij.execution.configurations.*;
import com.intellij.execution.filters.TextConsoleBuilderFactory;
import com.intellij.execution.junit.RefactoringListeners;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.util.JavaParametersUtil;
import com.intellij.openapi.components.PathMacroManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.options.SettingsEditor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.impl.JavaSdkImpl;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.DefaultJDOMExternalizer;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.listeners.RefactoringElementListener;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author Iskander Akishev
 */
public class JCHRunConfiguration extends ModuleBasedConfiguration<JavaRunConfigurationModule>
		implements CommonJavaRunConfigurationParameters, SingleClassConfiguration, RefactoringListenerProvider
{
	public String SOLUTION_CLASS_NAME;
	public String VM_PARAMETERS;
	public String PROGRAM_PARAMETERS;
	public String WORKING_DIRECTORY;
	public boolean ALTERNATIVE_JRE_PATH_ENABLED;
	public String ALTERNATIVE_JRE_PATH;
	private Map<String,String> myEnvs = new LinkedHashMap<String, String>();
	public boolean PASS_PARENT_ENVS = true;

	public JCHRunConfiguration(final String name, final Project project, JCHRunConfigurationType configurationType) {
		super(name, new JavaRunConfigurationModule(project, true), configurationType.getConfigurationFactories()[0]);
	}

	public void setMainClass(final PsiClass psiClass) {
		final Module originalModule = getConfigurationModule().getModule();
		setMainClassName(JavaExecutionUtil.getRuntimeQualifiedName(psiClass));
		setModule(JavaExecutionUtil.findModule(psiClass));
		restoreOriginalModule(originalModule);
	}

	public RunProfileState getState(@NotNull final Executor executor, @NotNull final ExecutionEnvironment env) throws ExecutionException {
		final JavaCommandLineState state = new MyJavaCommandLineState(env);
		state.setConsoleBuilder(TextConsoleBuilderFactory.getInstance().createBuilder(getProject()));
		return state;
	}

	public SettingsEditor<? extends RunConfiguration> getConfigurationEditor() {
		return new JCHRunConfigurable(getProject());
	}

	@Override
	@Nullable
	public String getGeneratedName() {
		if (SOLUTION_CLASS_NAME == null) return null;
		return JavaExecutionUtil.getPresentableClassName(SOLUTION_CLASS_NAME, getConfigurationModule());
	}

	public RefactoringElementListener getRefactoringElementListener(final PsiElement element) {
		final RefactoringElementListener listener = RefactoringListeners.
				getClassOrPackageListener(element, new RefactoringListeners.SingleClassConfigurationAccessor(this));
		return RunConfigurationExtension.wrapRefactoringElementListener(element, this, listener);
	}

	@Nullable
	public PsiClass getMainClass() {
		return getConfigurationModule().findClass(SOLUTION_CLASS_NAME);
	}

	public boolean isGeneratedName() {
		if (SOLUTION_CLASS_NAME == null || SOLUTION_CLASS_NAME.length() == 0) {
			return JavaExecutionUtil.isNewName(getName());
		}
		return Comparing.equal(getName(), getGeneratedName());
	}

	public String suggestedName() {
		if (SOLUTION_CLASS_NAME == null || SOLUTION_CLASS_NAME.length() == 0) {
			return getName();
		}
		return ProgramRunnerUtil.shortenName(JavaExecutionUtil.getShortClassName(SOLUTION_CLASS_NAME), 0);
	}

	public void setMainClassName(final String qualifiedName) {
		final boolean generatedName = isGeneratedName();
		SOLUTION_CLASS_NAME = qualifiedName;
		if (generatedName) setName(getGeneratedName());
	}

	public void checkConfiguration() throws RuntimeConfigurationException {
		if (ALTERNATIVE_JRE_PATH_ENABLED){
			if (ALTERNATIVE_JRE_PATH == null ||
					ALTERNATIVE_JRE_PATH.length() == 0 ||
					!JavaSdkImpl.checkForJre(ALTERNATIVE_JRE_PATH)){
				throw new RuntimeConfigurationWarning(ExecutionBundle.message("jre.path.is.not.valid.jre.home.error.mesage", ALTERNATIVE_JRE_PATH));
			}
		}
		final JavaRunConfigurationModule configurationModule = getConfigurationModule();
		final PsiClass psiClass = configurationModule.checkModuleAndClassName(SOLUTION_CLASS_NAME, "No solution class specified");
		if (!JCHUtil.isSolutionClass(psiClass)) {
			throw new RuntimeConfigurationWarning("'" + SOLUTION_CLASS_NAME + "' is not a valid solution class");
		}

		RunConfigurationExtension.checkConfigurationIsValid(this);
	}

	public void setVMParameters(String value) {
		VM_PARAMETERS = value;
	}

	public String getVMParameters() {
		return VM_PARAMETERS;
	}

	public void setProgramParameters(String value) {
		PROGRAM_PARAMETERS = value;
	}

	public String getProgramParameters() {
		return PROGRAM_PARAMETERS;
	}

	public void setWorkingDirectory(String value) {
		WORKING_DIRECTORY = ExternalizablePath.urlValue(value);
	}

	public String getWorkingDirectory() {
		return ExternalizablePath.localPathValue(WORKING_DIRECTORY);
	}

	public void setPassParentEnvs(boolean passParentEnvs) {
		PASS_PARENT_ENVS = passParentEnvs;
	}

	public boolean isPassParentEnvs() {
		return PASS_PARENT_ENVS;
	}

	@NotNull
	public Map<String, String> getEnvs() {
		return myEnvs;
	}

	public void setEnvs(@NotNull final Map<String, String> envs) {
		this.myEnvs = envs;
	}

	@Nullable
	public String getRunClass() {
		return SOLUTION_CLASS_NAME;
	}

	@Nullable
	public String getPackage() {
		return null;
	}

	public boolean isAlternativeJrePathEnabled() {
		return ALTERNATIVE_JRE_PATH_ENABLED;
	}

	public void setAlternativeJrePathEnabled(boolean enabled) {
		this.ALTERNATIVE_JRE_PATH_ENABLED = enabled;
	}

	public String getAlternativeJrePath() {
		return ALTERNATIVE_JRE_PATH;
	}

	public void setAlternativeJrePath(String path) {
		this.ALTERNATIVE_JRE_PATH = path;
	}

	public Collection<Module> getValidModules() {
		return JavaRunConfigurationModule.getModulesForClass(getProject(), SOLUTION_CLASS_NAME);
	}

	protected ModuleBasedConfiguration createInstance() {
		return new JCHRunConfiguration(getName(), getProject(), JCHRunConfigurationType.getInstance());
	}

	public void readExternal(final Element element) throws InvalidDataException {
		PathMacroManager.getInstance(getProject()).expandPaths(element);
		super.readExternal(element);
		RunConfigurationExtension.readSettings(this, element);
		DefaultJDOMExternalizer.readExternal(this, element);
		readModule(element);
		EnvironmentVariablesComponent.readExternal(element, getEnvs());
	}

	public void writeExternal(final Element element) throws WriteExternalException {
		super.writeExternal(element);
		RunConfigurationExtension.writeSettings(this, element);
		DefaultJDOMExternalizer.writeExternal(this, element);
		writeModule(element);
		EnvironmentVariablesComponent.writeExternal(element, getEnvs());
		PathMacroManager.getInstance(getProject()).collapsePathsRecursively(element);
	}

	private class MyJavaCommandLineState extends JavaCommandLineState {
		public MyJavaCommandLineState(final ExecutionEnvironment environment) {
			super(environment);
		}

		protected JavaParameters createJavaParameters() throws ExecutionException {
			final JavaParameters params = new JavaParameters();
			final int classPathType = JavaParametersUtil.getClasspathType(getConfigurationModule(), SOLUTION_CLASS_NAME, false);
			JavaParametersUtil.configureModule(getConfigurationModule(), params, classPathType, ALTERNATIVE_JRE_PATH_ENABLED ? ALTERNATIVE_JRE_PATH : null);
			JavaParametersUtil.configureConfiguration(params, JCHRunConfiguration.this);

			params.setMainClass(SolutionRunner.class.getName());
			params.getProgramParametersList().add(SOLUTION_CLASS_NAME);

			return params;
		}
	}
}
